#ifndef __uhf_kenny_kfjiweur89u83r33e3e__
#define __uhf_kenny_kfjiweur89u83r33e3e__

#include <atomic>
#include <thread>
#include <functional>

#include <hude/base/log.hpp>
#include <hude/base/hex.hpp>
#include <hude/aio/context.hpp>
#include <hude/utils/parses.hpp>
#include <hude/base/optional.hpp>
#include <hude/utils/serialport.hpp>

#include "packet.hpp"

namespace pgl
{
    using namespace x2000;

    /*********************************************************************
     * @brief uhf操作封闭类
     */
    template< typename _aio = hude::aio::aio_t<> >
    class uhf_t
    {
    public:
        using aio_t       = _aio;
        using this_t      = uhf_t< _aio >;
        using sport_t     = hude::utils::serialport_t;
        using parses_t    = hude::utils::parses_t< uint8_t, 0U >;
        using buffer_t    = typename parses_t::buffer_t;
        using citr_t      = typename parses_t::citr_t;
        using on_raw_t    = typename parses_t::drop_t;
        using on_tag_t    = std::function< void( const x2000::tag_t& ) >;

    public:
        uhf_t( aio_t aio, on_raw_t drop = on_raw_t(), on_raw_t input = on_raw_t(), on_raw_t output = on_raw_t() ) :
            aio_( aio ), on_drop_( drop ), on_input_( input ), on_output_( output ), parsing_( false )
        {
        }

        bool open( const char*, int );

        void close();

        template< size_t _timeout = 3000U >
        hude::optional_t< uint8_t > state();

        template< size_t _timeout = 3000U >
        bool stop();

        template< x2000::mid_t _mid, size_t _timeout = 3000U >
        hude::optional_t< typename pkt_res_t< _mid >::type_t > get();

        template< x2000::mid_t _mid, size_t _timeout = 3000U, typename ... _args >
        bool set( _args&& ... args );

        template< size_t _timeout = 0U >
        bool scan( on_tag_t on_tag, uint32_t ants, bool fast );

        template< size_t _timeout = 3000U, typename ... _args >
        hude::optional_t< uint8_t > write( _args&& ... args );

        template< x2000::mid_t _mid, size_t _timeout = 3000U, typename ... _args >
        hude::optional_t< typename pkt_res_t< _mid >::type_t > cmd( _args&& ... args );

    public:
        inline bool operator()( const buffer_t& buffer ) { return contral( buffer ); }
        inline parses_t* parses() { return &parses_; }

        bool contral( const buffer_t& );

    private:
        void recv( uint32_t events );
        void parse();

        template< x2000::mid_t _mid, typename _res, size_t _timeout = 3000U, typename ... _args >
        bool command( _res& res, _args&& ... args );

    private:
        aio_t            aio_;
        sport_t          sport_;
        parses_t         parses_;
        on_raw_t         on_drop_;
        on_raw_t         on_input_;
        on_raw_t         on_output_;
        std::atomic_flag parsing_;
    };

    /*********************************************************************
     * @brief ...
     */
    template< typename _aio, mid_t _mid >
    struct diallog_base_t
    {
        using _uhf_t      = uhf_t< _aio >;
        using req_t       = typename pkt_req_t< _mid >::type_t;
        using this_t      = diallog_base_t< _aio, _mid >;
        using observe_t   = hude::utils::sync_observe_t< _uhf_t, typename _uhf_t::parses_t >;
        using result_t    = typename observe_t::result_t;
        using duration_t  = std::chrono::system_clock::duration;

        _uhf_t& uhf_;
        const duration_t timeout_;

        diallog_base_t( _uhf_t& uhf, const duration_t& timeout ) :
            uhf_( uhf ), timeout_( timeout ) {}

        inline typename observe_t::result_t dialog( const req_t& req, typename observe_t::on_reply_t on_reply )
        {
            observe_t observe( &uhf_, uhf_.parses() );

            return observe.request( req.to_buffer(), on_reply, timeout_ );
        }
    };

    /*********************************************************************
     * @brief ...
     */
    template< typename _aio, mid_t _mid, typename _res = typename pkt_res_t< _mid >::type_t >
    struct diallog_command_t :
        public diallog_base_t< _aio, _mid >
    {
        using base_t  = diallog_base_t< _aio, _mid >;
        using _res_t  = typename _res::token_t;
        using reply_t = _res;

        diallog_command_t( typename base_t::_uhf_t& uhf, const typename base_t::duration_t& timeout ) :
            base_t( uhf, timeout ) {}

        template< typename ... _args >
        inline bool dialog( _res& res, _args&& ... args )
        {
            using citr_t  = typename base_t::_uhf_t::citr_t;

            auto on_frame = []( reply_t* res, citr_t b, citr_t e ) -> typename base_t::result_t
            {
                auto head = (const packet_t*)&(*(b+1));

                uint8_t vo = head->option();
                uint8_t vm = head->mid();

                const size_t siz = e - b;

                if( reply_t::emSIZE != 0U && siz != reply_t::emSIZE )
                {
                    return base_t::result_t::emRET_UNKNOWN;
                }

                if( vo == _res_t::emOPTION &&
                    vm == _res_t::emMID )
                {
                    head->copy_to( res );

                    return base_t::result_t::emRET_SUCCESS;
                }

                if( vo == packet_err_t::token_t::emOPTION &&
                    vm == packet_err_t::token_t::emMID )
                {
                    if( head->mid() == (mid_t)_res_t::emMID ) return base_t::result_t::emRET_REQUEST;
                }

                return base_t::result_t::emRET_UNKNOWN;
            };

            typename base_t::req_t req( std::forward< _args >( args ) ... );

            auto ret = base_t::dialog( req, std::bind( on_frame, &res, std::placeholders::_1, std::placeholders::_2 ) );

            return ( ret == base_t::observe_t::result_t::emRET_SUCCESS );
        }
    };

    /*********************************************************************
     * @brief ...
     */
    template< typename _aio >
    struct diallog_scan_t :
        public diallog_base_t< _aio, mid_t::emREAD >
    {
        using base_t = diallog_base_t< _aio, mid_t::emREAD >;

        diallog_scan_t( typename base_t::_uhf_t& uhf, const typename base_t::duration_t& timeout ) :
            base_t( uhf, timeout ) {}

        inline hude::optional_t< uint8_t > dialog( std::function< void( const tag_t& ) > on_tag, uint32_t ants, bool fast )
        {
            using _res_t = _token_t< 0x00, 0x02, mid_t::emREAD >;
            using _end_t = _token_t< 0x01, 0x02, mid_t::emREAD_END >;
            using _ant_t = _token_t< 0x01, 0x02, mid_t::emREAD_ANT >;
            using _tag_t = _token_t< 0x01, 0x02, mid_t::emREAD_TAG >;
            using citr_t = typename base_t::_uhf_t::citr_t;

            auto on_frame = []( std::function< void( const tag_t& ) > on_tag, uint8_t& mode, citr_t b, citr_t e ) -> typename base_t::result_t
            {
                auto head = (const packet_t*)&(*(b+1));

                uint8_t vo = head->option();
                uint8_t vm = head->mid();

                if( vo == _tag_t::emOPTION &&
                    vm == _tag_t::emMID )
                {
                    auto pkt = (const packet_evt_t< _tag_t >*)head;

                    tag_t tag;

                    if( pkt->tag( tag ) )
                        on_tag( tag );

                    return base_t::result_t::emRET_WAITTING;
                }

                if( vo == _ant_t::emOPTION &&
                    vm == _ant_t::emMID )
                {
                    return base_t::result_t::emRET_WAITTING;
                }

                if( vo == _res_t::emOPTION &&
                    vm == _res_t::emMID )
                {
                    auto pkt = (const packet_res_t< _res_t >*)head;

                    return pkt->result()? base_t::result_t::emRET_REQUEST : base_t::result_t::emRET_WAITTING;
                }

                if( vo == _end_t::emOPTION &&
                    vm == _end_t::emMID )
                {
                    auto pkt = (const packet_evt_t< _end_t >*)head;

                    mode = pkt->result();

                    return base_t::result_t::emRET_SUCCESS;
                }

                if( vo == packet_err_t::token_t::emOPTION &&
                    vm == packet_err_t::token_t::emMID )
                {
                    auto pkt = (const packet_err_t*)head;

                    if( pkt->mid() == (mid_t)_res_t::emMID ) return base_t::result_t::emRET_REQUEST;
                }

                return base_t::result_t::emRET_UNKNOWN;
            };

            uint8_t mode = 0;
            
            bool keep = (this->timeout_ == typename std::chrono::system_clock::duration{});

            typename base_t::req_t req( ants, keep, fast, false );

            auto ret = base_t::dialog( req, std::bind( on_frame, on_tag, std::ref( mode ), std::placeholders::_1, std::placeholders::_2 ) );

            return ( ret == base_t::observe_t::result_t::emRET_SUCCESS )?
                hude::optional_t< uint8_t >{ mode } : hude::optional_t< uint8_t >{};
        }
    };

    /*********************************************************************
     * @brief uhf操作封闭类
     */
    template< typename _aio >
    bool uhf_t< _aio >::open( const char* dev, int speed )
    {
        if( ! sport_.open( dev, false ) || ! sport_.settings( speed ) )
        {
            llog_err( "<plug-x2000>", "ERROR: operation uart port in uhf_t::open()" );

            return false;
        }

        auto ret = aio_->evt_commit( sport_.handle(), std::bind( &this_t::recv, this, std::placeholders::_1 ) );

        if( ret != 0 )
        {
            llog_err( "<plug-x2000>", "ERROR: add uart port to epoll in uhf_t::open()" );

            return false;
        }

        return true;
    }

    /*********************************************************************
     * @brief uhf操作封闭类
     */
    template< typename _aio >
    void uhf_t< _aio >::close()
    {
        aio_->evt_remove( sport_.handle() );

        sport_.close();
    }

    /*********************************************************************
     * @brief uhf操作封闭类
     */
    template< typename _aio >
    bool uhf_t< _aio >::contral( const buffer_t& buffer )
    {
        if( on_output_ ) on_output_( buffer.begin(), buffer.end() );

        const size_t siz = buffer.size();
        auto ret = sport_.write( &(buffer[0]), siz );

        return ( ret < 0 )? false : siz == (size_t)ret;
    }

    /*********************************************************************
     * @brief uhf操作封闭类
     */
    template< typename _aio >
    void uhf_t< _aio >::recv( uint32_t events )
    {
        // llog_dug( "<plug-x2000>", "\t\t BEGIN: AIO event for uhf recv( %u )", events );

        if( events & hude::aio::epoll_evt_t::emEPOLLIN )
        {
            const auto capacity = packet_t::emMAX_PKG;

            auto item = parses_.buffer_alloc( capacity );

            auto& buffer = *item;

            size_t count = 0;

            while( count < capacity )
            {
                auto ret = sport_.read( (char*)&(buffer[count]), capacity - count );

                if( ret <= 0 ) break;

                count += ret;

                // waitting more data...
                // std::this_thread::sleep_for( std::chrono::nanoseconds( 100U ) );
            }

            buffer.resize( count );

            parses_.buffer_push( item );

            if( on_input_ ) on_input_( buffer.begin(), buffer.end() );
                
            // 在异步任务池执行缓冲区数据解析
            if( count ) aio_->task_commit( [=]{ this->parse(); } );
        }

        // llog_dug( "<plug-x2000>", "\t\t END: AIO event for uhf recv( %u )", events );
    }

    /*********************************************************************
     * @brief 解析缓冲中存在的所有数据
     */
    template< typename _aio >
    inline void uhf_t< _aio >::parse()
    {
        if( ! parsing_.test_and_set() )
        {
            parses_.parse_all( &packet_t::find< typename parses_t::citr_t >, on_drop_ );

            parsing_.clear();
        }
    }

    /*********************************************************************
     * @brief uhf指令操作
     */
    template< typename _aio >
    template< x2000::mid_t _mid, typename _res , size_t _timeout, typename ... _args >
    bool uhf_t< _aio >::command( _res& res, _args&& ... args )
    {
        auto fn = [=]( _res& res, _args&& ... args ) -> bool
        {
            auto to = ( _timeout == 0 )? std::chrono::milliseconds( _timeout ) : parses_t::FOREVER;

            diallog_command_t< _aio, _mid, _res > dc( *this, to );

            auto ret = dc.dialog( res, std::forward< _args >( args ) ... );

            if( ! ret ) { llog_err( "<plug-x2000>", "ERROR: request command( %#x ) dialog of uhf.", _mid ); }

            return ret;
        };

        bool ret;

        return  ( ret = fn( res, std::forward< _args >( args ) ... ) ) ||
                ( ret = fn( res, std::forward< _args >( args ) ... ) ) ||
                ( ret = fn( res, std::forward< _args >( args ) ... ) );
    }

    /*********************************************************************
     * @brief uhf指令请求
     */
    template< typename _aio >
    template< x2000::mid_t _mid, size_t _timeout, typename ... _args >
    inline hude::optional_t< typename pkt_res_t< _mid >::type_t > uhf_t< _aio >::cmd( _args&& ... args )
    {
        using res_t = typename pkt_res_t< _mid >::type_t;

        hude::optional_t< res_t > res( res_t{} ); 

        auto ret = this->command< _mid, res_t, _timeout >( *res, std::forward< _args >( args ) ... );

        return ret? res : hude::optional_t< res_t >{};
    }

    /*********************************************************************
     * @brief uhf状态
     */
    template< typename _aio >
    template< size_t _timeout >
    inline hude::optional_t< uint8_t > uhf_t< _aio >::state()
    {
        packet_err_t res; 

        auto ret = this->command< mid_t::emSTATE, packet_err_t, _timeout >( res );

        return ret? hude::optional_t< uint8_t >{ res.state() } : hude::optional_t< uint8_t >{};
    }

    /*********************************************************************
     * @brief uhf停止
     */
    template< typename _aio >
    template< size_t _timeout >
    inline bool uhf_t< _aio >::stop()
    {
        auto ret = this->cmd< mid_t::emSTOP, _timeout >();

        return ret? ret->result() == 0U : false;
    }

    /*********************************************************************
     * @brief uhf查询
     */
    template< typename _aio >
    template< x2000::mid_t _mid, size_t _timeout >
    inline hude::optional_t< typename pkt_res_t< _mid >::type_t > uhf_t< _aio >::get()
    {
        return this->cmd< _mid, _timeout >();
    }

    /*********************************************************************
     * @brief uhf设置
     */
    template< typename _aio >
    template< mid_t _mid, size_t _timeout, typename ... _args >
    inline bool uhf_t< _aio >::set( _args&& ... args )
    {
        using res_t = typename pkt_res_t< _mid >::type_t;

        res_t res; 

        auto ret = this->command< _mid, res_t, _timeout >( res, std::forward< _args >( args ) ... );

        return ret && ( res.result() == 0U );
    }

    /*********************************************************************
     * @brief uhf盘存
     */
    template< typename _aio >
    template< size_t _timeout >
    bool uhf_t< _aio >::scan( on_tag_t on_tag, uint32_t ants, bool fast )
    {
        auto fn = [=]() -> hude::optional_t< uint8_t >
        {
            auto to = ( _timeout != 0 )? std::chrono::milliseconds( _timeout ) : parses_t::FOREVER;

            diallog_scan_t< _aio > ds( *this, to );

            auto ret = ds.dialog( on_tag, ants, fast );

            if( ! ret ) { llog_err( "<plug-x2000>", "ERROR: request scan uhf." ); }

            return ret;
        };

        hude::optional_t< uint8_t > ret;

        return ( ret = fn() ) || ( ret = fn() ) || ( ret = fn() );
    }

    /*********************************************************************
     * @brief uhf写操作
     */
    template< typename _aio >
    template< size_t _timeout, typename ... _args >
    hude::optional_t< uint8_t > uhf_t< _aio >::write( _args&& ... args )
    {
        using res_t = typename pkt_res_t< x2000::mid_t::emWRITE >::type_t;

        res_t res; 

        auto ret = this->command< x2000::mid_t::emWRITE, res_t, _timeout >( res, std::forward< _args >( args ) ... );

        return ret? hude::optional_t< uint8_t >{ res.error() } : hude::optional_t< uint8_t >{};
    }

} // namespace pgl

#endif // __uhf_kenny_kfjiweur89u83r33e3e__
